package server;
import java.io.*;
import java.util.*;
import msgs.*;
import javax.swing.*;
import java.awt.*;
/**
 * This is the class that is run directly as a new process from the admin tools. It pretty much just spawns two threads for listening
 * to the incoming connections.
 * @author Andrew Hayworth
 * @version 1.1
 */
public class TTServer implements TTConstants {
	protected ClientListener cl;
	protected ClientSender cs;
	private ClientParser cp;
	private AdminListener al;
	private AdminSender as;
	private AdminParser ap;
	// The PMHandler class takes care of this already.
	//protected static OfflineMsgHandler omh;
	private Looper lp;
	protected static String[] admins = { "ash8744", "rwk6160", "nxp2545" ,"bjp6152" } ;	// this is the default, unless it's changed.
	
	
	// this is the table of connections, key(username) val (ip address (string))
	protected static Hashtable<String, ConnectedUser> connTable;
	
	protected static Hashtable<String, Long> lastTalkTime;
	
	// admins connected
	protected static Hashtable<String, ConnectedUser> adminTable;
	
	//blockList is keyed on username of the person who does not wish
	//to see other certain users.
	protected static Hashtable<String, ArrayList<String>> blockList;
	
	// this is keyed on a username. when a signoff signon ocurrs, the
	// user signing off is looked up in this table and then everyone that is 
	// interested in him or her gets a notification
	protected static Hashtable<String, ArrayList<String>> interestedIn;
	
	// this is the offline pm db
	// key(username who has offline pms coming to them) value(name of sender, list of msgs)
	protected static Hashtable<String, Hashtable<String, ArrayList<String>>> offlinePm;
	
	// running list of chats
	protected static Hashtable<String, Chat> chats;
	
	// those who are banhammered.
	protected static ArrayList<String> banHammer;
	
	/**
	 * Reads conf files, forks off threads.
	 * No commandline args!
	 */
	public static void main(String[] args) {
		int adminport = 0;
		int userport = 0;
		int adminportsend = 0;
		int userportsend = 0;
		try {
			//read in our conf file, parse it.
			File conf = new File("conf/tigertalk.conf");
			BufferedReader br = new BufferedReader(new FileReader(conf));
			String[] ports = br.readLine().split(" ");
			adminport = Integer.parseInt(ports[0]);
			adminportsend = Integer.parseInt(ports[1]);
			userport = Integer.parseInt(ports[2]);
			userportsend = Integer.parseInt(ports[3]);
			admins = br.readLine().split(" ");
		}
		catch (Exception e) {
			System.err.println("Configuration file un-readable, or missing. Re-run the admintools to generate one!");
			System.out.println("Using default admin ports of : 5555, 5556");
			System.out.println("Using default client ports of: 6666, 6667");
			System.out.println("Default admins are ash8744, rwk6160, nxp2545, bjp6152");
			// use the defaults!
			adminport = 5555;
			adminportsend = 5556;
			userport = 6666;
			userportsend = 6667;
			// admins is set by default, unless it's changed by a file read.
		}
		finally {
			new TTServer(adminport, adminportsend, userport, userportsend, admins);	// don't really understand WHY I can't just start threads here ....
		}

	}
	
	/**
	 * Starts the listeners for Client and Admin connections.
	 * @param adminport Desired admin port #, read in from conf file
	 * @param userport Desired user port # , read in from conf file.
	 * @param admins List of admins read in from conf file.
	 */
	public TTServer(int adminport, int adminportsend, int userport, int userportsend, String[] admins) {
		// hopefully this sanity check should never fail...
		if (adminport == 0) System.exit(0);
		
		TTServer.admins = admins;
		
		// initialize our hashtables
		connTable = new Hashtable<String, ConnectedUser>();
		interestedIn = new Hashtable<String, ArrayList<String>>();
		chats = new Hashtable<String, Chat>();
		lastTalkTime = new Hashtable<String, Long>();
		adminTable = new Hashtable<String, ConnectedUser>();
		
		blockList = (Hashtable<String, ArrayList<String>>) readHash(new File("data/blockList.dat"));
		if (blockList == null) {
			blockList = new Hashtable<String, ArrayList<String>>();
			File f = new File("data");
			if (!(f.exists())) f.mkdir();
			writeHash(new File("data/blockList.dat"), blockList);
		}
		
		offlinePm = (Hashtable<String, Hashtable<String,ArrayList<String>>>) readHash(new File("data/offlinePm.dat"));
		if (offlinePm == null) {
			offlinePm = new Hashtable<String, Hashtable<String,ArrayList<String>>>();
			File f = new File("data");
			if (!(f.exists())) f.mkdir();
			writeHash(new File("data/offlinePm.dat"), offlinePm);
		}
		
		banHammer = (ArrayList<String>) readObject(new File("data/banHammer.dat"));
		if (banHammer == null) {
			banHammer = new ArrayList<String>();
			File f = new File("data");
			if (!(f.exists())) f.mkdir();
			writeObject(new File("data/banHammer.dat"), banHammer);
		}
		
		cl = new ClientListener(userport);
		cl.start();
		 
		cp = new ClientParser();
		cp.start();
		
		cs = new ClientSender(userportsend);
		cs.start();
		
		al = new AdminListener(adminport);
		al.start();
		
		as = new AdminSender(adminportsend);
		as.start();
		
		ap = new AdminParser();
		ap.start();
		
		// PM Handler class takes care of this.
		//omh = new OfflineMsgHandler();
		//omh.start();
		
		lp = new Looper();
		lp.start();
		
		
	}
	
	/**
	 * This function just reads a hashtable object from the disk and returns it as a generic hashtable
	 * dont forget to cast!
	 * @param file filename to read from
	 * @return Hashtable from file.....
	 */
	public Hashtable readHash(File file) {
		return (Hashtable) readObject(file);
	}
	
	/**
	 * This just writes a generic hashtable to a file....
	 * @param file filename to write to
	 * @param h hashtable to write
 	 * @return true if write good, false if write bad.
	 */
	public static boolean writeHash(File file, Hashtable h) {
		return writeObject(file, h);
	}

	/**
	 * Reads an object from disk
	 * @param file file where it is stored
	 * @return desired object within file
	 */
	public Object readObject(File file) {
		Object o;
		try {
			ObjectInputStream read = new ObjectInputStream(new FileInputStream(file));
			synchronized(TTConstants.lock) { o = read.readObject();; }
			read.close();
			return o;
		}
		catch (Exception e) {
			return null;
		}
	}
	
	/**
	 * Writes an object out to disk
	 * @param file File to write to
	 * @param o Object to write
	 * @return true if write  good, false if write bad
	 */
	public static boolean writeObject(File file, Object o) {
		try {
			ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));
			synchronized(TTConstants.lock) { out.writeObject(o); }
			return true;
		}
		catch (Exception e) { return false; }
	}
}
